Skip to content

feat(hugr-py): Allow linking packages and modules from Python#2947

Merged
maximilianruesch merged 17 commits intomainfrom
mr/feat/py-linking
Mar 17, 2026
Merged

feat(hugr-py): Allow linking packages and modules from Python#2947
maximilianruesch merged 17 commits intomainfrom
mr/feat/py-linking

Conversation

@maximilianruesch
Copy link
Contributor

Adds capabilities to hugr-py to allow linking modules and entire packages together. This is a required feature for Guppy libraries, found in Quantinuum/guppylang#1481, and could be considered the core feature for the same.

hugr-core needed a slight modification to allow reading a module from an envelope (with all the builtin validation around expecting a single module) while also returning the extensions present in the envelope. The functions are exposed via pyo3 bindings to Python and tested there.

@codecov
Copy link

codecov bot commented Mar 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.85%. Comparing base (93aac28) to head (705bc20).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2947      +/-   ##
==========================================
+ Coverage   83.84%   83.85%   +0.01%     
==========================================
  Files         267      267              
  Lines       52923    52953      +30     
  Branches    46854    46869      +15     
==========================================
+ Hits        44371    44402      +31     
+ Misses       6283     6282       -1     
  Partials     2269     2269              
Flag Coverage Δ
python 88.80% <100.00%> (+0.04%) ⬆️
rust 83.20% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@maximilianruesch maximilianruesch marked this pull request as ready for review March 16, 2026 15:28
@maximilianruesch maximilianruesch requested a review from a team as a code owner March 16, 2026 15:28
@maximilianruesch
Copy link
Contributor Author

Note: I am unsure where to add tests for hugr-core, and which tests to add specifically. Currently, the function is covered by hugr-py tests implicitly.

Also, one could consider whether we should refactor load to call load_with_exts and just ignore the exsts.

.store_with_exts(&mut result, EnvelopeConfig::binary(), &exts_into)
.unwrap();

hugr_core::package::Package::load(&result[..], None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like something we don't want in production, I'd say make it a debug assert, but not sure how that works with maturin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oop yeah, that was something that was used for debugging. I will have a look at debug asserts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to work as you would expect in regular rust. I pushed it to a debug statement.

}

#[pyfunction]
fn link_modules(module_into: &[u8], module_from: &[u8]) -> PyResult<Vec<u8>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice that we're going to have all three hugrs in memory, which seems like it could be a problem.
I don't really have strong enough rust/pyO3 foo to say the solution, but possibly:

  1. take as arguments impl Reader so the files are read here and their contents can be dropped
  2. Take Package as argument so we can consume them when they're linked in and release the memory

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could instead take an owned Vec<u8> if that helps... See https://pyo3.rs/v0.28.2/conversions/tables.html for allowed types / types that have an implementation of the required traits out of the box.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that'd be good

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified the param. It seems that the reader trait to read into a hugr is not implemented for a vec though, so I still use a slice. It might also not make much of a difference since Python has no concept of ownership, so I presume the contents are copied anyway. This might actually be more expensive now 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I benchmarked it, and using the QAOA example using Vec<u8 I had about 0.048s per link call (from Python), whereas using &[u8] gave me about 0.031s per link call (probably due to avoiding an unnecessary copy. I reverted the change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's fair 👍

Copy link
Contributor

@croyzor croyzor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@maximilianruesch maximilianruesch added this pull request to the merge queue Mar 17, 2026
Merged via the queue into main with commit 329c243 Mar 17, 2026
30 checks passed
@maximilianruesch maximilianruesch deleted the mr/feat/py-linking branch March 17, 2026 15:51
This was referenced Mar 17, 2026
github-merge-queue bot pushed a commit that referenced this pull request Mar 18, 2026
…export (#2964)

Does:
- Correct / add missing parent module names to the existing modules (as
far as I know this is mainly for the specifier displayed when printing
the module in e.g. a Python REPL)
- Correct module definitions for exception classes (again, I think this
only has display / introspection impact)
- Add a missing reexport of an exception class in the linking module
(and add a test for it from Python to ensure it exists).

Related to #2947
github-merge-queue bot pushed a commit that referenced this pull request Mar 19, 2026
## 🤖 New release

* `hugr-model`: 0.26.0 -> 0.26.1
* `hugr-core`: 0.26.0 -> 0.26.1 (✓ API compatible changes)
* `hugr-llvm`: 0.26.0 -> 0.26.1
* `hugr`: 0.26.0 -> 0.26.1 (✓ API compatible changes)
* `hugr-passes`: 0.26.0 -> 0.26.1 (✓ API compatible changes)
* `hugr-cli`: 0.26.0 -> 0.26.1
* `hugr-persistent`: 0.5.0 -> 0.5.1

<details><summary><i><b>Changelog</b></i></summary><p>

## `hugr-model`

<blockquote>

##
[0.26.0](hugr-model-v0.25.7...hugr-model-v0.26.0)
- 2026-03-16

### Testing

- Replace model text snapshots with roundtrip tests
([#2933](#2933))
- Add missing width arg in model-call example
([#2945](#2945))
</blockquote>

## `hugr-core`

<blockquote>

##
[0.26.1](hugr-core-v0.26.0...hugr-core-v0.26.1)
- 2026-03-18

### New Features

- *(hugr-py)* Allow linking packages and modules from Python
([#2947](#2947))

### Refactor

- start cleaning up SiblingSubgraph convexity checking
([#2956](#2956))
</blockquote>

## `hugr-llvm`

<blockquote>

##
[0.26.0](hugr-llvm-v0.25.7...hugr-llvm-v0.26.0)
- 2026-03-16

### New Features

- *(llvm)* [**breaking**] Upgrade to LLVM 21
([#2901](#2901))
- Include private HUGR functions in the local symbol table
([#2831](#2831))
- [**breaking**] TypeRow: add impl From array of Type, remove From<Type>
([#2784](#2784))

### Refactor

- [**breaking**] Remove deprecated Value::Function
([#2928](#2928))
- [**breaking**] Remove deprecated stack_array codegen
([#2929](#2929))
</blockquote>

## `hugr`

<blockquote>

##
[0.26.1](hugr-v0.26.0...hugr-v0.26.1)
- 2026-03-18

### Bug Fixes

- ConstantFold fails when the module contains function declarations
([#2954](#2954))

### New Features

- *(hugr-py)* Allow linking packages and modules from Python
([#2947](#2947))

### Refactor

- start cleaning up SiblingSubgraph convexity checking
([#2956](#2956))
</blockquote>

## `hugr-passes`

<blockquote>

##
[0.26.1](hugr-passes-v0.26.0...hugr-passes-v0.26.1)
- 2026-03-18

### Bug Fixes

- ConstantFold fails when the module contains function declarations
([#2954](#2954))
</blockquote>

## `hugr-cli`

<blockquote>

##
[0.26.0](hugr-cli-v0.25.7...hugr-cli-v0.26.0)
- 2026-03-16

### Documentation

- Move `spec/schema` and `spec/std_extensions` to `resources/`
([#2897](#2897))

### New Features

- [**breaking**] Rename ModelText envelope format to SExpression
([#2927](#2927))
- [**breaking**] TypeRow: add impl From array of Type, remove From<Type>
([#2784](#2784))
</blockquote>

## `hugr-persistent`

<blockquote>

##
[0.5.0](hugr-persistent-v0.4.7...hugr-persistent-v0.5.0)
- 2026-03-16

### New Features

- *(llvm)* [**breaking**] Upgrade to LLVM 21
([#2901](#2901))
- [**breaking**] TypeRow: add impl From array of Type, remove From<Type>
([#2784](#2784))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants